{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Transparent Fallback\n",
    "\n",
    "This part of tutorial is also available in step-by-step notebook version on [github](https://github.com/dmlc/minpy/blob/master/examples/tutorials/transparent_fallback.ipynb). Please try it out!\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Concept of transparent fallback\n",
    "\n",
    "Since MinPy fully integrates MXNet, it allows you to use GPU to speed up your algorithm with only minor change, while keeping the familia NumPy syntax. \n",
    "\n",
    "However, NumPy is a giant library with many of operators, each may have different calling conventions with different parameters. MXNet's GPU operators are only a subset of them. Therefore, it is inevitable that you may use some functions that are currently missing on the GPU side. \n",
    "\n",
    "To solve this problem, MinPy designed a policy system to determine which implementation shoud be applied, consisted of build-in policies in `minpy.dispatch.policy` (also aliased in minpy root):\n",
    "\n",
    "- `PreferMXNetPolicy()` [**Default**]: Prefer MXNet. Use NumPy as a transparent fallback, which wil be discussed below.\n",
    "- `OnlyNumPyPolicy()`: Only use NumPy operations.\n",
    "- `OnlyMXNetPolicy()`: Only use MXNet operations.\n",
    "- `BlacklistPolicy()`: Discussed below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The default policy `PreferMXNetPolicy` gracefully adopts the NumPy implementation once the operator is missing on GPU side, and handles the memory copies among GPU and CPU for you, illustrated with the following chart:\n",
    "\n",
    "![PreferMXNetPolicy](images/PreferMXNetPolicy.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The code below will prove this for you."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[32mI1110 11:11:21 12022 minpy.array:_synchronize_data:423]\u001b[0m Copy from MXNet array to NumPy array for Array \"4580105904\" of shape (10L, 20L).\n",
      "\u001b[32mI1110 11:11:21 12022 minpy.array:_synchronize_data:429]\u001b[0m Copy from NumPy array to MXNet array for Array \"4580229360\" of shape (10, 20).\n"
     ]
    }
   ],
   "source": [
    "import minpy.numpy as np\n",
    "# First turn on the logging to know what happens under the hood.\n",
    "import logging\n",
    "logging.getLogger('minpy.array').setLevel(logging.DEBUG)\n",
    "\n",
    "# x is created as a MXNet array\n",
    "x = np.zeros((10, 20))\n",
    "\n",
    "\n",
    "# `cosh` is currently missing in MXNet's GPU implementation.\n",
    "# So `x` will fallback to a NumPy array, so you will see a \n",
    "# logging like \"Copy from MXNet array to NumPy array...\", then\n",
    "# NumPy's implementation of `cosh` will be called to get the\n",
    "# result `y` as a NumPy array. But you don't need to worry \n",
    "# about the memory copy from GPU -> CPU\n",
    "y = np.cosh(x)\n",
    "\n",
    "\n",
    "# `log` has MXNet's GPU implementation, so it will copy the \n",
    "# array `y` from NumPy array to MXNet array and you will see\n",
    "# a logging like \"Copy from NumPy array to MXNet array...\"\n",
    "# Once again, you don't need to worry about it. It is transparent.\n",
    "z = np.log(y)\n",
    "\n",
    "\n",
    "# Turn off the logging.\n",
    "logging.getLogger('minpy.array').setLevel(logging.WARN)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, there are a few of NumPy functions cannot work properly even in the `PreferMXNetPolicy`, due to the difference between NumPy and MXNet interface. Here is one example with different parameter types:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "ename": "MXNetError",
     "evalue": "Invalid Parameter format for loc expect float but value='<mxnet.ndarray.NDArray object at 0x11101d190>'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mMXNetError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-2-3e8f056001e5>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m     16\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0my\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     17\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0mgaussian_cluster_generator\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m10000\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m500\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31m...\u001b[0m\n",
      "\u001b[0;32m/Users/ATlaS/Library/PyEnvs/minpy/lib/python2.7/site-packages/mxnet-0.7.0-py2.7.egg/mxnet/base.pyc\u001b[0m in \u001b[0;36mcheck_call\u001b[0;34m(ret)\u001b[0m\n\u001b[1;32m     75\u001b[0m     \"\"\"\n\u001b[1;32m     76\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mret\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 77\u001b[0;31m         \u001b[0;32mraise\u001b[0m \u001b[0mMXNetError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpy_str\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_LIB\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMXGetLastError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     78\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     79\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0msys\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mversion_info\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mMXNetError\u001b[0m: Invalid Parameter format for loc expect float but value='<mxnet.ndarray.NDArray object at 0x11101d190>'"
     ]
    }
   ],
   "source": [
    "# Uner PreferMXNetPolicy, np.random.normal will redirect to MXNet's implementation\n",
    "# but it does not support mu and sigma to be arrays (only scalar\n",
    "# is supported right now).\n",
    "import minpy.numpy as np\n",
    "def gaussian_cluster_generator(num_samples=10000, num_features=500, num_classes=5):\n",
    "    mu = np.random.rand(num_classes, num_features)\n",
    "    sigma = np.ones((num_classes, num_features)) * 0.1\n",
    "    num_cls_samples = num_samples / num_classes\n",
    "    x = np.zeros((num_samples, num_features))\n",
    "    y = np.zeros((num_samples, num_classes))\n",
    "    for i in range(num_classes):\n",
    "        # this line will occur an error\n",
    "        cls_samples = np.random.normal(mu[i,:], sigma[i,:], (num_cls_samples, num_features))\n",
    "        x[i*num_cls_samples:(i+1)*num_cls_samples] = cls_samples\n",
    "        y[i*num_cls_samples:(i+1)*num_cls_samples,i] = 1\n",
    "    return x, y\n",
    "\n",
    "gaussian_cluster_generator(10000, 500, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What that means is we must control dispatch at a finer granularity. We design another blacklist machinism for you. The operator in the blacklist will fallback to its numpy implementaiton and the content of blacklist will be prepared when you install MinPy automatically. This will solve most of these problems. \n",
    "\n",
    "The procedure of function call under `PerferMXNetPolicy` will become:\n",
    "\n",
    "![Blacklist](images/PreferMXNetPolicyWithBlacklist.png)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The default blacklist is generated by testing the calls in [this file](https://github.com/dmlc/minpy/blob/master/minpy/utils/blacklist_generator.py). The test may not be complete, therefore you can run your code iteratively and generate a customized blacklist under `AutoBlacklistPolicy`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import minpy\n",
    "p = minpy.AutoBlacklistPolicy(gen_rule=True, append_rule=True)\n",
    "set_global_policy(p)\n",
    "\n",
    "# under AutoBlacklistPolicy, operators throwing exception will be\n",
    "# added into the blacklist, then MinPy will call the NumPy \n",
    "# implementation next time to avoid this kind of exception.\n",
    "with p:\n",
    "    gaussian_cluster_generator(10000, 500, 5)\n",
    "    \n",
    "# this will not occur error afterwards\n",
    "gaussian_cluster_generator(10000, 500, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!--- Although we've provided some function to you for switching policy easily (see [Select Policy for Operations](https://minpy.readthedocs.io/en/latest/feature/policy.html)), you really don't need to consider this unless you meet a rare situation that current policy doesn't work properly, -->\n",
    "\n",
    "Do check [\"Pitfalls when working together with NumPy\"](http://minpy.readthedocs.io/en/latest/feature/limitation.html) for known issues. If you encounter another, please raise an issue in our github! "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
